package com.zgh.opcua.manager;

import com.zgh.opcua.cert.KeyStoreLoader;
import com.zgh.opcua.config.OpcContext;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfig;
import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfigBuilder;
import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider;
import org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider;
import org.eclipse.milo.opcua.stack.client.DiscoveryClient;
import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
import org.springframework.util.StringUtils;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;

import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;

/**
 * @ClassName OpcUaClientInstance
 * @Description Opc客户端
 * @Author Administrator
 * @Date 10:22 2025/11/5
 * @Version 1.0
 */
@Slf4j
public class OpcUaClientInstance {

    private final ScheduledExecutorService reconnectScheduler = Executors.newSingleThreadScheduledExecutor();

    // 使用volatile关键字确保多线程环境下的可见性
    private static volatile OpcUaClient opcUaClient;

    // 唯一的单例实例
    private static final OpcUaClientInstance INSTANCE = new OpcUaClientInstance();

    // 私有构造函数，防止外部创建多个实例
    private OpcUaClientInstance() {
    }

    // 获取类实例的静态方法
    public static OpcUaClientInstance getInstance() {
        return INSTANCE;
    }

    // 获取 OpcUaClient 实例的方法
    public OpcUaClient getOpcUaClient() throws Exception {
        // 双重检查锁定模式
        if (opcUaClient == null) {
            synchronized (OpcUaClientInstance.class) {
                if (opcUaClient == null) {
                    String url = OpcContext.getEndpointUrl();
                    String security = OpcContext.getSecurity();;
                    String username = OpcContext.getIdpUsername();
                    String password = OpcContext.getIdpPassword();
                    opcUaClient = createClient(url, security, username, password);
                    // 连接到服务器
                    opcUaClient.connect().get(5, TimeUnit.SECONDS);
                    //检测心跳
                    startReconnectLoop();
                    log.info("OPC UA Client 单例创建成功");
                }
            }
        }
        return opcUaClient;
    }


    // 可选：提供一个使用自定义参数创建客户端的方法
    public OpcUaClient getOpcUaClient(String url, String security, String username, String password) throws Exception {
        if (opcUaClient == null) {
            synchronized (OpcUaClientInstance.class) {
                if (opcUaClient == null) {
                    opcUaClient = createClient(url, security, username, password);
                    // 连接到服务器
                    opcUaClient.connect().get(5, TimeUnit.SECONDS);
                    //自动检测心跳
                    startReconnectLoop();
                    log.info("OPC UA Client instance created with custom parameters");
                }
            }
        }
        return opcUaClient;
    }

    private void startReconnectLoop() {

        reconnectScheduler.scheduleAtFixedRate(() -> {
            try {
                if (!opcUaClient.getSession().isDone()) {
                    log.warn(" 客户端断开，尝试重新连接...");
                    opcUaClient.disconnect().get(3, TimeUnit.SECONDS); // 安全断开
                    opcUaClient.connect().get(5, TimeUnit.SECONDS);
                    log.info("重新连接成功");
                    //restoreSubscriptions();
                }
            } catch (Exception e) {
                log.error("重连失败: {}", e.getMessage());
            }
        }, 2, 5, TimeUnit.SECONDS); // 每 5 秒重试一次
    }

    /**
     * @return
     * @throws Exception
     * @MethodName: createClient
     * @Description: 创建客户端
     * @CreateTime 2019年12月18日 下午4:02:54
     */
    private OpcUaClient createClient(String url, String security, String username, String password) throws Exception {
        Path securityTempDir = Paths.get(System.getProperty("java.io.tmpdir"), "security");
        Files.createDirectories(securityTempDir);
        if (!Files.exists(securityTempDir)) {
            throw new Exception("unable to create security dir: " + securityTempDir);
        }
        log.info("security temp dir: {}", securityTempDir.toAbsolutePath());
        KeyStoreLoader loader = new KeyStoreLoader().load(securityTempDir);
        SecurityPolicy securityPolicy = getSecurityPolicy(security);
        String uri = securityPolicy.getUri();
        ;
        final String str_uri = uri;
        List<EndpointDescription> endpoints = null;
        try {
            endpoints = DiscoveryClient.getEndpoints(url).get();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        EndpointDescription endpoint = endpoints.stream()
                .filter(e -> e.getSecurityPolicyUri().equals(str_uri))
                .filter(endpointFilter())
                .findFirst()
                .orElseThrow(() -> new Exception("no desired endpoints returned"));
        //创建客户端连接
        OpcUaClient opcUaClient = getOpcUaClient(loader, endpoint, username, password);
        return opcUaClient;
    }

    /**
     * 获取安全策略对象
     *
     * @param security 安全策略名称
     * @return SecurityPolicy对象
     */
    private SecurityPolicy getSecurityPolicy(String security) {
        if ("Basic128Rsa15".equals(security)) {
            return SecurityPolicy.Basic128Rsa15;
        } else if ("Basic256".equals(security)) {
            return SecurityPolicy.Basic256;
        } else if ("Basic256Sha256".equals(security)) {
            return SecurityPolicy.Basic256Sha256;
        } else if ("Aes128_Sha256_RsaOaep".equals(security)) {
            return SecurityPolicy.Aes128_Sha256_RsaOaep;
        } else if ("Aes256_Sha256_RsaPss".equals(security)) {
            return SecurityPolicy.Aes256_Sha256_RsaPss;
        } else {
            return SecurityPolicy.None;
        }
    }

    private OpcUaClient getOpcUaClient(KeyStoreLoader loader, EndpointDescription endpoint, String username, String password) throws Exception {

        OpcUaClientConfigBuilder opcUaClientConfigBuilder = OpcUaClientConfig.builder()
                .setApplicationName(LocalizedText.english(""))
                .setApplicationUri("")
                .setCertificate(loader.getClientCertificate())
                .setKeyPair(loader.getClientKeyPair())
                .setEndpoint(endpoint)
                .setRequestTimeout(uint(5000));
        if (!StringUtils.isEmpty(username) && !StringUtils.isEmpty(password)) {
            opcUaClientConfigBuilder.setIdentityProvider(new UsernameProvider(username, password));
        } else {
            opcUaClientConfigBuilder.setIdentityProvider(new AnonymousProvider());
        }
        opcUaClientConfigBuilder.setKeepAliveInterval(uint(5000));
        OpcUaClientConfig config = opcUaClientConfigBuilder.build();
        return OpcUaClient.create(config);
    }

    /**
     * @return Predicate<EndpointDescription>
     * @MethodName: endpointFilter
     * @Description: 端点过滤器
     * @CreateTime 2019年12月18日 下午4:06:22
     */
    private Predicate<EndpointDescription> endpointFilter() {
        return e -> true; // 默认接受所有端点，可根据需要修改
    }

    /**
     * 关闭客户端连接并重置实例
     */
    public void shutdown() {
        if (opcUaClient != null) {
            try {
                opcUaClient.disconnect().get();
                log.info("OPC UA Client 已经断开");
            } catch (Exception e) {
                log.error(" OPC UA Client 断开连接失败", e);
            } finally {
                opcUaClient = null;
            }
        }
    }
}
